﻿namespace NTLib.Pipe.Windows.Containers
{
    using Core.Manipulators;
    using Core.Threading;
    using Descriptors;
    using Exceptions;
    using Pipe.Containers;
    using Resources;
    using Streaming;
    using Streaming.Containers;
    using Streaming.Descriptors.Packages;
    using Streaming.Packages;
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.IO.Pipes;
    using System.Linq;
    using System.Security.AccessControl;
    using System.Security.Principal;
    using System.Text;
    using System.Threading;
    using System.Threading.Tasks;

    /// <summary>
    /// Pipe using the microsoft <see cref="NamedPipeClientStream"/> to create a communication
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <seealso cref="NTLib.Pipe.Containers.BasePipeSourcePoolItem{T, NTLib.Pipe.Windows.Descriptors.NamedPipeTargetDescriptor}" />
    public class NamedPipeSource<T> : BaseStreamingPipeSourcePoolItem<T, NamedPipeTargetDescriptor>
    {
        #region Fields

        private readonly SemaphoreLocker _locker;

        private PipeStream _pipeStream;

        #endregion

        #region Ctor

        /// <summary>
        /// Initializes a new instance of the <see cref="NamedPipeSource{T}"/> class.
        /// </summary>
        public NamedPipeSource()
        {
            this._locker = new SemaphoreLocker();
        }

        #endregion

        #region Methods

        /// <summary>
        /// Initializes the current pipe asynchronously.
        /// THis method is called after the formater check
        /// </summary>
        /// <param name="token">The token.</param>
        /// <returns>
        /// Task used to make this initialization asynchronously.
        /// </returns>
        protected override async Task OnInitializeAfterCheckAsync(CancellationToken token)
        {
            if (string.IsNullOrWhiteSpace(this.TargetTyped.PipeName))
            {
                throw new PipeNotInitializedException(InnerErrorMessages.PipeNamedCantBeEmpty);
            }

            PipeDirection direction = PipeDirection.InOut;
            if (this.PipeType == PipeType.FromTarget)
            {
                direction = PipeDirection.In;
            }
            else if (this.PipeType == PipeType.OneWay)
            {
                direction = PipeDirection.Out;
            }

            int timeout = 4200;
            if (this.TargetTyped.ConnectionTimeOut != null)
            {
                timeout = (int)this.TargetTyped.ConnectionTimeOut.Value.TotalMilliseconds;
            }

            if (this.TargetTyped.IsServerHost)
            {
                var server = new NamedPipeServerStream(this.TargetTyped.PipeName,
                                                       direction,
                                                       1,
                                                       PipeTransmissionMode.Byte,
                                                       PipeOptions.Asynchronous);

                server.BeginWaitForConnection(PipeConnectionOccured, server);
                Timer timeoutTime = null;
                timeoutTime = new Timer((e) =>
                {
                    using (this._locker.ScopeLock())
                    {
                        if (Status != PipeStatus.Ready)
                        {
                            DeleteServer(server);
                            timeoutTime.Dispose();
                        }
                    }
                }, server, timeout, Timeout.Infinite);
                using (this._locker.ScopeLock())
                {
                    this._pipeStream = server;
                }
            }
            else
            {
                var clientPipe = new NamedPipeClientStream(".", this.TargetTyped.PipeName, direction);
                await clientPipe.ConnectAsync(timeout, token);
                if (clientPipe.IsConnected)
                {
                    using (this._locker.ScopeLock())
                    {
                        this._pipeStream = clientPipe;
                        this.Status = PipeStatus.Ready;
                    }
                }
            }
        }

        /// <summary>
        /// Determines whether this instance can read package asynchronous
        /// </summary>
        /// <param name="token">The token.</param>
        /// <returns></returns>
        /// <remarks>
        /// Multiple called can occured simultianiously
        /// </remarks>
        protected override Task<bool> CanReadPackageAsync(CancellationToken token)
        {
            return Task.FromResult(Status == PipeStatus.Ready && this._pipeStream.IsConnected && !this._pipeStream.SafePipeHandle.IsClosed);
        }

        /// <summary>
        /// Determines whether this instance can send the package.
        /// </summary>
        /// <param name="item">The item.</param>
        /// <param name="token">The token.</param>
        /// <returns>
        /// <c>true</c> if this instance can send the package the specified item; otherwise, <c>false</c>.
        /// </returns>
        /// <remarks>
        /// Multiple called can occured simultianiously
        /// </remarks>
        protected override Task<bool> CanSendPackageAsync(IPackage item, CancellationToken token)
        {
            return Task.FromResult(Status == PipeStatus.Ready && this._pipeStream.IsConnected && !this._pipeStream.SafePipeHandle.IsClosed);
        }

        /// <summary>
        /// Reads the new item using the package formater.
        /// </summary>
        /// <param name="descriptor"></param>
        /// <param name="token">The token.</param>
        protected override async Task<IPackage> ReadAsync(IPackageDescriptor descriptor, CancellationToken token)
        {
            if (this._pipeStream != null)
            {
                SimplePackage pkg = SimplePackage.Create();

                await pkg.LoadAsync(descriptor, this._pipeStream, token);
                return pkg;
            }

            return null;
        }

        /// <summary>
        /// Sends the package asynchronous.
        /// </summary>
        /// <param name="package">The package.</param>
        /// <param name="token">The token.</param>
        /// <remarks>
        ///     Multiple called can occured simultianiously
        /// </remarks>
        protected override async Task SendPackageAsync(IPackage package, CancellationToken token)
        {
            if (this._pipeStream != null && package != null)
            {
                package.Stream.Seek(0, SeekOrigin.Begin);

                int count = (int)package.Stream.Length;
                byte[] bytes = new byte[count];

                await package.Stream.ReadAsync(bytes, 0, count);
                await this._pipeStream.WriteAsync(bytes, 0, count);
            }
        }

        /// <summary>
        /// Call when another element connect to the namedPipe
        /// </summary>
        private void PipeConnectionOccured(IAsyncResult ar)
        {
            var server = ar.AsyncState as NamedPipeServerStream;

            if (server == null)
            {
                return;
            }

            server.EndWaitForConnection(ar);
            if (server != this._pipeStream)
            {
                DeleteServer(server);
                return;
            }

            using (this._locker.ScopeLock())
            {
                Status = PipeStatus.Ready;
            }
        }

        /// <summary>
        /// Deletes the server.
        /// </summary>
        /// <param name="server">The server.</param>
        private static void DeleteServer(NamedPipeServerStream server)
        {
            try
            {
                server.Close();
            }
            catch
            {

            }
            try
            {
                server.Dispose();
            }
            catch
            {

            }
        }

        /// <summary>
        /// Manageds the dispose.
        /// </summary>
        protected override void ManagedDispose()
        {
            OnCleanContent();
            base.ManagedDispose();
        }

        /// <summary>
        /// Called when [pool item reset].
        /// </summary>
        protected override void OnPoolItemReset()
        {
            OnCleanContent();
            base.OnPoolItemReset();

            this.Status = PipeStatus.NotInitialize;
        }

        /// <summary>
        /// Cleans the content.
        /// </summary>
        protected override void OnCleanContent()
        {
            if (_pipeStream != null)
            {
                var tmp = this._pipeStream;
                this._pipeStream = null;
                try
                {
                    tmp.Close();
                }
                catch
                {

                }

                try
                {
                    tmp.Dispose();
                }
                catch
                {

                }
            }
            this.Status = PipeStatus.Closed;
        }

        /// <summary>
        /// Determines whether if the pipe source is alive.
        /// </summary>
        /// <returns>
        /// <c>true</c> if the pipe source is alive; otherwise, <c>false</c>.
        /// </returns>
        protected override bool IsPipeSourceAlive()
        {
            return this._pipeStream != null &&
                   this._pipeStream.SafePipeHandle != null &&
                   !this._pipeStream.SafePipeHandle.IsClosed &&
                   !this._pipeStream.SafePipeHandle.IsInvalid &&
                   this._pipeStream.IsConnected;
        }

        #endregion
    }
}
